import * as THREE from 'three';
import {
  AmbientLight,
  AxesHelper,
  BufferGeometry,
  Clock,
  Color,
  DirectionalLight,
  DoubleSide,
  Geometry,
  Material,
  MathUtils,
  Mesh,
  Path,
  PlaneBufferGeometry,
  Points,
  PointsMaterial,
  Shape,
  SphereBufferGeometry,
  Texture,
  Vector3,
} from 'three';
import BaseThreeApp from '../BaseThreeApp';
import DebugInfo from '../DebugInfo';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { ExtrudeGeometryOptions } from 'three/src/geometries/ExtrudeBufferGeometry';

const worldCenter = new Vector3(0, 0, 0);
const frameMaterial = new THREE.MeshBasicMaterial({
  color: 0x111011,
  side: DoubleSide,
  depthTest: false,
  depthWrite: false,
  transparent: true,
  wireframe: true,
});

class _13ShapeTest extends BaseThreeApp {
  public scene: THREE.Scene;
  public camera: THREE.Camera;
  public renderer: THREE.WebGLRenderer;
  public controller: OrbitControls;
  public geos: (Geometry | BufferGeometry)[] = [];
  public mats: Material[] = [];
  public texs: Texture[] = [];
  public clock: Clock;
  public group: THREE.Group;

  start() {
    this.scene = new THREE.Scene();
    this.camera = new THREE.PerspectiveCamera(
      60,
      this.clientWidth / this.clientHeight,
      0.1,
      3000
    );
    this.resetCamera();

    const material = new THREE.MeshLambertMaterial({ color: 0x00ffff });
    this.mats.push(material);
    const buf = new SphereBufferGeometry(0.1);
    this.geos.push(buf);
    this.scene.add(new Mesh(buf, material));
    const axisHelp = new AxesHelper(5);
    this.geos.push(axisHelp.geometry);
    this.scene.add(axisHelp);
    this.controller = new OrbitControls(this.camera, this.element);
    this.controller.target.copy(worldCenter);
    this.controller.update();

    this.renderer = new THREE.WebGLRenderer({
      canvas: this.element,
    });
    this.renderer.setDrawingBufferSize(this.clientWidth, this.clientHeight, 1);

    const dirLight = new DirectionalLight(0xffffff, 0.6);
    dirLight.position.set(1000, 1000, 1000);
    dirLight.target.position.set(0, 0, 0);
    this.scene.add(dirLight);

    const dirLight2 = new DirectionalLight(0xffffff, 0.3);
    dirLight2.position.set(-1000, -1000, -1000);
    this.scene.add(dirLight2);

    const ambient = new AmbientLight(0xffffff, 0.2);
    this.scene.add(ambient);

    const planeBuf = new PlaneBufferGeometry(1000, 1000, 500, 500);
    this.geos.push(planeBuf);
    const matPlane = new PointsMaterial({ color: 0x212323, size: 0.05 });
    const plane = new Points(planeBuf, matPlane);
    plane.rotation.x = MathUtils.degToRad(90);
    this.scene.add(plane);

    this.group = new THREE.Group();
    this.group.position.y = 50;
    this.scene.add(this.group);

    // this.newShape1();
    // this.newShape2();
    this.newShape3();

    this.clock = new Clock(true);
    this.isRunning = true;
    requestAnimationFrame(this.render.bind(this));
  }

  private newShape1(): void {
    const x = 0,
      y = 0;

    const heartShape = new THREE.Shape();

    heartShape.moveTo(x + 5, y + 5);
    heartShape.bezierCurveTo(x + 5, y + 5, x + 4, y, x, y);
    heartShape.bezierCurveTo(x - 6, y, x - 6, y + 7, x - 6, y + 7);
    heartShape.bezierCurveTo(x - 6, y + 11, x - 3, y + 15.4, x + 5, y + 19);
    heartShape.bezierCurveTo(x + 12, y + 15.4, x + 16, y + 11, x + 16, y + 7);
    heartShape.bezierCurveTo(x + 16, y + 7, x + 16, y, x + 10, y);
    heartShape.bezierCurveTo(x + 7, y, x + 5, y + 5, x + 5, y + 5);

    const geometry = new THREE.ShapeBufferGeometry(heartShape);
    this.geos.push(geometry);
    const material = new THREE.MeshBasicMaterial({
      color: 0x00ff00,
      side: DoubleSide,
    });
    this.mats.push(material);
    const mesh = new THREE.Mesh(geometry, material);
    const frameMesh = new Mesh(geometry, frameMaterial);
    this.scene.add(mesh);
    this.scene.add(frameMesh);
  }

  private newShape2() {
    const closedSpline = new THREE.CatmullRomCurve3(
      [
        new THREE.Vector3(-60, -100, 60),
        new THREE.Vector3(-60, 20, 60),
        new THREE.Vector3(-60, 120, 60),
        new THREE.Vector3(60, 20, -60),
        new THREE.Vector3(60, -100, -60),
      ],
      true,
      'catmullrom'
    );

    // closedSpline.curveType = 'catmullrom';
    // closedSpline.closed = true;

    const extrudeSettings1 = {
      steps: 100,
      bevelEnabled: false,
      extrudePath: closedSpline,
    };

    const pts1 = [],
      count = 3;

    for (let i = 0; i < count; i++) {
      const l = 20;
      const a = ((2 * i) / count) * Math.PI;
      pts1.push(new THREE.Vector2(Math.cos(a) * l, Math.sin(a) * l));
    }
    const shape1 = new THREE.Shape(pts1);

    const geometry1 = new THREE.ExtrudeBufferGeometry(shape1, extrudeSettings1);
    this.geos.push(geometry1);
    const material1 = new THREE.MeshLambertMaterial({
      color: 0xb00000,
      wireframe: false,
    });
    this.mats.push(material1);
    const mesh1 = new THREE.Mesh(geometry1, material1);
    this.scene.add(mesh1);

    //

    const randomPoints = [];
    for (let i = 0; i < 10; i++) {
      randomPoints.push(
        new THREE.Vector3(
          (i - 4.5) * 50,
          THREE.MathUtils.randFloat(-50, 50),
          THREE.MathUtils.randFloat(-50, 50)
        )
      );
    }
    const randomSpline = new THREE.CatmullRomCurve3(randomPoints);

    //

    const extrudeSettings2 = {
      steps: 200,
      bevelEnabled: false,
      extrudePath: randomSpline,
    };
    const pts2 = [],
      numPts = 5;
    for (let i = 0; i < numPts * 2; i++) {
      const l = i % 2 == 1 ? 10 : 20;
      const a = (i / numPts) * Math.PI;
      pts2.push(new THREE.Vector2(Math.cos(a) * l, Math.sin(a) * l));
    }

    const shape2 = new THREE.Shape(pts2);
    const geometry2 = new THREE.ExtrudeBufferGeometry(shape2, extrudeSettings2);
    this.geos.push(geometry2);
    const material2 = new THREE.MeshLambertMaterial({
      color: 0xff8000,
      wireframe: false,
    });
    this.mats.push(material2);
    const mesh2 = new THREE.Mesh(geometry2, material2);
    this.scene.add(mesh2);

    //

    const materials = [material1, material2];
    const extrudeSettings3 = {
      depth: 20,
      steps: 1,
      bevelEnabled: true,
      bevelThickness: 2,
      bevelSize: 4,
      bevelSegments: 1,
    };

    const geometry3 = new THREE.ExtrudeBufferGeometry(shape2, extrudeSettings3);
    this.geos.push(geometry3);
    const mesh3 = new THREE.Mesh(geometry3, materials);
    mesh3.position.set(50, 100, 50);

    this.scene.add(mesh3);
  }

  private newShape3() {
    // California

    const californiaPts: THREE.Vector2[] = [];

    californiaPts.push(new THREE.Vector2(610, 320));
    californiaPts.push(new THREE.Vector2(450, 300));
    californiaPts.push(new THREE.Vector2(392, 392));
    californiaPts.push(new THREE.Vector2(266, 438));
    californiaPts.push(new THREE.Vector2(190, 570));
    californiaPts.push(new THREE.Vector2(190, 600));
    californiaPts.push(new THREE.Vector2(160, 620));
    californiaPts.push(new THREE.Vector2(160, 650));
    californiaPts.push(new THREE.Vector2(180, 640));
    californiaPts.push(new THREE.Vector2(165, 680));
    californiaPts.push(new THREE.Vector2(150, 670));
    californiaPts.push(new THREE.Vector2(90, 737));
    californiaPts.push(new THREE.Vector2(80, 795));
    californiaPts.push(new THREE.Vector2(50, 835));
    californiaPts.push(new THREE.Vector2(64, 870));
    californiaPts.push(new THREE.Vector2(60, 945));
    californiaPts.push(new THREE.Vector2(300, 945));
    californiaPts.push(new THREE.Vector2(300, 743));
    californiaPts.push(new THREE.Vector2(600, 473));
    californiaPts.push(new THREE.Vector2(626, 425));
    californiaPts.push(new THREE.Vector2(600, 370));
    californiaPts.push(new THREE.Vector2(610, 320));

    for (let i = 0; i < californiaPts.length; i++) {
      californiaPts[i].multiplyScalar(0.25);
    }

    const californiaShape = new THREE.Shape(californiaPts);

    // Triangle

    const triangleShape = new THREE.Shape()
      .moveTo(80, 20)
      .lineTo(40, 80)
      .lineTo(120, 80)
      .lineTo(80, 20); // close path

    // Heart

    const x = 0,
      y = 0;

    const heartShape = new THREE.Shape() // From http://blog.burlock.org/html5/130-paths
      .moveTo(x + 25, y + 25)
      .bezierCurveTo(x + 25, y + 25, x + 20, y, x, y)
      .bezierCurveTo(x - 30, y, x - 30, y + 35, x - 30, y + 35)
      .bezierCurveTo(x - 30, y + 55, x - 10, y + 77, x + 25, y + 95)
      .bezierCurveTo(x + 60, y + 77, x + 80, y + 55, x + 80, y + 35)
      .bezierCurveTo(x + 80, y + 35, x + 80, y, x + 50, y)
      .bezierCurveTo(x + 35, y, x + 25, y + 25, x + 25, y + 25);

    // Square

    const sqLength = 80;

    const squareShape = new THREE.Shape()
      .moveTo(0, 0)
      .lineTo(0, sqLength)
      .lineTo(sqLength, sqLength)
      .lineTo(sqLength, 0)
      .lineTo(0, 0);

    // Rounded rectangle

    const roundedRectShape = new THREE.Shape();

    (function roundedRect(ctx, x, y, width, height, radius) {
      ctx.moveTo(x, y + radius);
      ctx.lineTo(x, y + height - radius);
      ctx.quadraticCurveTo(x, y + height, x + radius, y + height);
      ctx.lineTo(x + width - radius, y + height);
      ctx.quadraticCurveTo(
        x + width,
        y + height,
        x + width,
        y + height - radius
      );
      ctx.lineTo(x + width, y + radius);
      ctx.quadraticCurveTo(x + width, y, x + width - radius, y);
      ctx.lineTo(x + radius, y);
      ctx.quadraticCurveTo(x, y, x, y + radius);
    })(roundedRectShape, 0, 0, 50, 50, 20);

    // Track

    const trackShape = new THREE.Shape()
      .moveTo(40, 40)
      .lineTo(40, 160)
      .absarc(60, 160, 20, Math.PI, 0, true)
      .lineTo(80, 40)
      .absarc(60, 40, 20, 2 * Math.PI, Math.PI, true);

    // Circle

    const circleRadius = 40;
    const circleShape = new THREE.Shape()
      .moveTo(0, circleRadius)
      .quadraticCurveTo(circleRadius, circleRadius, circleRadius, 0)
      .quadraticCurveTo(circleRadius, -circleRadius, 0, -circleRadius)
      .quadraticCurveTo(-circleRadius, -circleRadius, -circleRadius, 0)
      .quadraticCurveTo(-circleRadius, circleRadius, 0, circleRadius);

    // Fish

    const fishShape = new THREE.Shape()
      .moveTo(x, y)
      .quadraticCurveTo(x + 50, y - 80, x + 90, y - 10)
      .quadraticCurveTo(x + 100, y - 10, x + 115, y - 40)
      .quadraticCurveTo(x + 115, y, x + 115, y + 40)
      .quadraticCurveTo(x + 100, y + 10, x + 90, y + 10)
      .quadraticCurveTo(x + 50, y + 80, x, y);

    // Arc circle

    const arcShape = new THREE.Shape()
      .moveTo(50, 10)
      .absarc(10, 10, 40, 0, Math.PI * 2, false);

    const holePath = new THREE.Path()
      .moveTo(20, 10)
      .absarc(10, 10, 10, 0, Math.PI * 2, true);

    arcShape.holes.push(holePath);

    // Smiley

    const smileyShape = new THREE.Shape()
      .moveTo(80, 40)
      .absarc(40, 40, 40, 0, Math.PI * 2, false);

    const smileyEye1Path = new THREE.Path()
      .moveTo(35, 20)
      .absellipse(25, 20, 10, 10, 0, Math.PI * 2, true, 0);

    const smileyEye2Path = new THREE.Path()
      .moveTo(65, 20)
      .absarc(55, 20, 10, 0, Math.PI * 2, true);

    const smileyMouthPath = new THREE.Path()
      .moveTo(20, 40)
      .quadraticCurveTo(40, 60, 60, 40)
      .bezierCurveTo(70, 45, 70, 50, 60, 60)
      .quadraticCurveTo(40, 80, 20, 60)
      .quadraticCurveTo(5, 50, 20, 40);

    smileyShape.holes.push(smileyEye1Path);
    smileyShape.holes.push(smileyEye2Path);
    smileyShape.holes.push(smileyMouthPath);

    // Spline shape

    const splinepts = [];
    splinepts.push(new THREE.Vector2(70, 20));
    splinepts.push(new THREE.Vector2(80, 90));
    splinepts.push(new THREE.Vector2(-30, 70));
    splinepts.push(new THREE.Vector2(0, 0));

    const splineShape = new THREE.Shape().moveTo(0, 0).splineThru(splinepts);

    const extrudeSettings = {
      depth: 8,
      bevelEnabled: true,
      bevelSegments: 2,
      steps: 2,
      bevelSize: 1,
      bevelThickness: 1,
    };

    // addShape( shape, color, x, y, z, rx, ry,rz, s );

    this.addShape(
      californiaShape,
      extrudeSettings,
      0xf08000,
      -300,
      -100,
      0,
      0,
      0,
      0,
      1
    );
    this.addShape(
      triangleShape,
      extrudeSettings,
      0x8080f0,
      -180,
      0,
      0,
      0,
      0,
      0,
      1
    );
    this.addShape(
      roundedRectShape,
      extrudeSettings,
      0x008000,
      -150,
      150,
      0,
      0,
      0,
      0,
      1
    );
    this.addShape(
      trackShape,
      extrudeSettings,
      0x008080,
      200,
      -100,
      0,
      0,
      0,
      0,
      1
    );
    this.addShape(
      squareShape,
      extrudeSettings,
      0x0040f0,
      150,
      100,
      0,
      0,
      0,
      0,
      1
    );
    this.addShape(
      heartShape,
      extrudeSettings,
      0xf00000,
      60,
      100,
      0,
      0,
      0,
      Math.PI,
      1
    );
    this.addShape(
      circleShape,
      extrudeSettings,
      0x00f000,
      120,
      250,
      0,
      0,
      0,
      0,
      1
    );
    this.addShape(
      fishShape,
      extrudeSettings,
      0x404040,
      -60,
      200,
      0,
      0,
      0,
      0,
      1
    );
    this.addShape(
      smileyShape,
      extrudeSettings,
      0xf000f0,
      -200,
      250,
      0,
      0,
      0,
      Math.PI,
      1
    );
    this.addShape(arcShape, extrudeSettings, 0x804000, 150, 0, 0, 0, 0, 0, 1);
    this.addShape(
      splineShape,
      extrudeSettings,
      0x808080,
      -50,
      -100,
      0,
      0,
      0,
      0,
      1
    );

    this.addLineShape(arcShape.holes[0], 0x804000, 150, 0, 0, 0, 0, 0, 1);

    for (let i = 0; i < smileyShape.holes.length; i += 1) {
      this.addLineShape(
        smileyShape.holes[i],
        0xf000f0,
        -200,
        250,
        0,
        0,
        0,
        Math.PI,
        1
      );
    }
  }

  private addLineShape(shape: Path, color: number, x, y, z, rx, ry, rz, s) {
    // lines

    shape.autoClose = true;

    const points = shape.getPoints();
    const spacedPoints = shape.getSpacedPoints(50);

    const geometryPoints = new THREE.BufferGeometry().setFromPoints(points);
    const geometrySpacedPoints = new THREE.BufferGeometry().setFromPoints(
      spacedPoints
    );
    this.geos.push(geometryPoints);
    this.geos.push(geometrySpacedPoints);

    // solid line

    let line = new THREE.Line(
      geometryPoints,
      new THREE.LineBasicMaterial({ color: color })
    );
    line.position.set(x, y, z - 25);
    line.rotation.set(rx, ry, rz);
    line.scale.set(s, s, s);
    this.group.add(line);

    // line from equidistance sampled points

    line = new THREE.Line(
      geometrySpacedPoints,
      new THREE.LineBasicMaterial({ color: color })
    );
    line.position.set(x, y, z + 25);
    line.rotation.set(rx, ry, rz);
    line.scale.set(s, s, s);
    this.group.add(line);

    // vertices from real points

    let particles = new THREE.Points(
      geometryPoints,
      new THREE.PointsMaterial({ color: color, size: 4 })
    );
    particles.position.set(x, y, z + 75);
    particles.rotation.set(rx, ry, rz);
    particles.scale.set(s, s, s);
    this.group.add(particles);

    // equidistance sampled points

    particles = new THREE.Points(
      geometrySpacedPoints,
      new THREE.PointsMaterial({ color: color, size: 4 })
    );
    particles.position.set(x, y, z + 125);
    particles.rotation.set(rx, ry, rz);
    particles.scale.set(s, s, s);
    this.group.add(particles);
  }

  private addShape(
    shape: Shape,
    extrudeSettings: ExtrudeGeometryOptions,
    color: number,
    x,
    y,
    z,
    rx,
    ry,
    rz,
    s
  ) {
    const loader = new THREE.TextureLoader();
    const texture = loader.load('images/textures/uv_grid.jpg');
    this.texs.push(texture);

    // it's necessary to apply these settings in order to correctly display the texture on a shape geometry

    texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
    texture.repeat.set(0.008, 0.008);

    // flat shape with texture
    // note: default UVs generated by THREE.ShapeBufferGeometry are simply the x- and y-coordinates of the vertices

    let geometry = new THREE.ShapeBufferGeometry(shape);
    this.geos.push(geometry);

    let mesh = new THREE.Mesh(
      geometry,
      new THREE.MeshPhongMaterial({ side: THREE.DoubleSide, map: texture })
    );
    mesh.position.set(x, y, z - 175);
    mesh.rotation.set(rx, ry, rz);
    mesh.scale.set(s, s, s);
    this.group.add(mesh);

    // flat shape

    geometry = new THREE.ShapeBufferGeometry(shape);
    this.geos.push(geometry);
    mesh = new THREE.Mesh(
      geometry,
      new THREE.MeshPhongMaterial({ color: color, side: THREE.DoubleSide })
    );
    mesh.position.set(x, y, z - 125);
    mesh.rotation.set(rx, ry, rz);
    mesh.scale.set(s, s, s);
    this.group.add(mesh);

    //  wire frame

    mesh = new THREE.Mesh(
      geometry,
      new THREE.MeshPhongMaterial({
        color: color,
        side: THREE.DoubleSide,
        wireframe: true,
      })
    );
    mesh.position.set(x, y, z - 200);
    mesh.rotation.set(rx, ry, rz);
    mesh.scale.set(s, s, s);
    this.group.add(mesh);

    // extruded shape

    geometry = new THREE.ExtrudeBufferGeometry(shape, extrudeSettings);
    this.geos.push(geometry);
    mesh = new THREE.Mesh(
      geometry,
      new THREE.MeshPhongMaterial({ color: color })
    );
    mesh.position.set(x, y, z - 75);
    mesh.rotation.set(rx, ry, rz);
    mesh.scale.set(s, s, s);
    this.group.add(mesh);

    this.addLineShape(shape, color, x, y, z, rx, ry, rz, s);
  }

  public render(time: number) {
    if (!this.isRunning || this.element === null) {
      return;
    }
    DebugInfo.Instance.begin();

    this.renderer.render(this.scene, this.camera);
    DebugInfo.Instance.end();
    requestAnimationFrame(this.render.bind(this));
  }

  onresize(e: UIEvent) {
    super.onresize(e);
    console.log('onresive');
    if (this.renderer) {
      this.renderer.setDrawingBufferSize(
        this.clientWidth,
        this.clientHeight,
        1
      );
      if (this.camera && this.camera instanceof THREE.PerspectiveCamera) {
        this.camera.aspect = this.clientWidth / this.clientHeight;
        this.camera.updateProjectionMatrix();
      }
    }
  }

  protected resetCamera() {
    this.camera.position.set(50, 50, 50);
    this.camera.lookAt(worldCenter);
  }

  public onkeydown(e: KeyboardEvent): any | void {
    if (e.key === 'r' || ' ') {
      this.resetCamera();
    }
  }
  public oncontextmenu(e: MouseEvent): any | void {
    e.cancelBubble = true;
    e.returnValue = false;
    return false;
  }

  destory() {
    this.geos.forEach((v) => v.dispose());
    this.geos = [];
    this.mats.forEach((v) => v.dispose());
    this.mats = [];
    this.texs.forEach((v) => v.dispose());
    this.texs = [];
    super.destory();
  }
}

export default new _13ShapeTest();
